using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace MetaObjects.Core
{
	public static class PropertyAccessor<T>
	{
		private static class Delegates<TValue>
		{
			public static readonly object GettersSync = new object();
			public static readonly Dictionary<string, Func<T, TValue>> Getters = new Dictionary<string, Func<T, TValue>>();
			public static readonly object SettersSync = new object();
			public static readonly Dictionary<string, Action<T, TValue>> Setters = new Dictionary<string, Action<T, TValue>>();
		}

		public static Func<T, object> GetPropertyDelegate(string propertyName)
		{
			return GetPropertyDelegate<object>(propertyName);
		}

		public static Func<T, TValue> GetPropertyDelegate<TValue>(string propertyName)
		{
			if (Delegates<TValue>.Getters.ContainsKey(propertyName))
			{
				return Delegates<TValue>.Getters[propertyName];
			}
			ParameterExpression p = Expression.Parameter(typeof(T), "p");
			Expression<Func<T, TValue>> expression = Expression.Lambda<Func<T, TValue>>(Expression.Convert(Expression.Property(p, propertyName), typeof(TValue)), p);
			Func<T, TValue> func = expression.Compile();
			lock (Delegates<TValue>.GettersSync)
			{
				if (Delegates<TValue>.Getters.ContainsKey(propertyName))
				{
					return Delegates<TValue>.Getters[propertyName];
				}
				Delegates<TValue>.Getters.Add(propertyName, func);
			}
			return func;
		}

		public static Action<T, object> SetPropertyDelegate(string propertyName)
		{
			return SetPropertyDelegate<object>(propertyName);
		}

		public static Action<T, TValue> SetPropertyDelegate<TValue>(string propertyName)
		{
			if (Delegates<TValue>.Setters.ContainsKey(propertyName))
			{
				return Delegates<TValue>.Setters[propertyName];
			}
			PropertyInfo propertyInfo = typeof(T).GetProperty(propertyName);
			MethodInfo methodInfo = propertyInfo.GetSetMethod();
			ParameterExpression parameter = Expression.Parameter(typeof(T), "p");
			ParameterExpression value = Expression.Parameter(typeof(TValue), "v");
			MethodCallExpression methodCallExpression = Expression.Call(parameter, methodInfo, Expression.Convert(value, propertyInfo.PropertyType));
			Expression<Action<T, TValue>> expression = Expression.Lambda<Action<T, TValue>>(methodCallExpression, parameter, value);
			Action<T, TValue> action = expression.Compile();
			lock (Delegates<TValue>.SettersSync)
			{
				if (Delegates<TValue>.Setters.ContainsKey(propertyName))
				{
					return Delegates<TValue>.Setters[propertyName];
				}
				Delegates<TValue>.Setters.Add(propertyName, action);
			}
			return action;
		}
	}
}